/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nweb_inputmethod_handler.h"

#include <codecvt>

#include "arkweb/build/features/features.h"
#include "arkweb/chromium_ext/base/ohos/sys_info_utils_ext.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "cef/include/cef_task.h"
#include "content/public/browser/browser_thread.h"
#include "nweb_imf_cursor_info_adapter_impl.h"
#include "nweb_imf_input_attribute_adapter_impl.h"
#include "nweb_imf_selection_range_adapter_impl.h"
#include "nweb_imf_text_config_adapter_impl.h"
#include "libcef/browser/thread_util.h"
#include "ohos_glue/base/include/ark_web_errno.h"
#include "ohos_adapter_helper.h"
#include "res_sched_client_adapter.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"

namespace OHOS::NWeb {

static constexpr char16_t DEL_CHAR = 127;
constexpr int32_t MAX_ENTERKEYTYPE = 8;
constexpr float AVOID_OFFSET = 24.0;
const std::string AUTO_FILL_CANCEL_PRIVATE_COMMAND = "autofill.cancel";

class OnTextChangedListenerImpl : public IMFTextListenerAdapter {
 public:
  explicit OnTextChangedListenerImpl(NWebInputMethodHandler* handler)
      : handler_(handler) {}
  ~OnTextChangedListenerImpl() = default;

  // All following listenser callbacks should invoke call on UI thread
  void InsertText(const std::u16string& text) override {
    if (text.size() == 1 && text.front() == '\n') {
      return;
    }
    handler_->InsertText(text);
  }

  void DeleteForward(int32_t length) override {
    handler_->DeleteForward(length);
  }

  void DeleteBackward(int32_t length) override {
    handler_->DeleteBackward(length);
  }

  void SendKeyEventFromInputMethod() override {
    LOG(INFO) << "NWebInputMethodHandler::SendKeyEventFromInputMethod";
  }

  void SendKeyboardStatus(const IMFAdapterKeyboardStatus& status) override {
    if (status == IMFAdapterKeyboardStatus::SHOW) {
      handler_->SetIMEStatus(true);
    } else if (status == IMFAdapterKeyboardStatus::HIDE) {
      handler_->SetIMEStatus(false);
    }
  }

  void SendFunctionKey(
      std::shared_ptr<IMFAdapterFunctionKeyAdapter> functionKey) override {
    if (handler_ && functionKey) {
      LOG(DEBUG) << "SendFunctionKey enterkeytype = "
                 << static_cast<int32_t>(functionKey->GetEnterKeyType());
      handler_->SendEnterKeyEvent(
          static_cast<int32_t>(functionKey->GetEnterKeyType()));
    }
  }

  void SetKeyboardStatus(bool status) override {
    handler_->SetIMEStatus(status);
  }

  void KeyboardUpperRightCornerHide() override {
    handler_->WebBlurKeyboardHide();
  }

  void WebSetImeShow(bool visible) override {
    handler_->WebSetImeShow(visible);
  }

  void MoveCursor(const IMFAdapterDirection direction) override {
    if (direction == IMFAdapterDirection::NONE) {
      LOG(ERROR) << "NWebInputMethodHandler::MoveCursor got none direction";
      return;
    }
    handler_->MoveCursor(direction);
  }

  void HandleSetSelection(int32_t start, int32_t end) override {}

  void HandleExtendAction(int32_t action) override {
    handler_->HandleExtendAction(action);
  }

  void HandleSelect(int32_t keyCode, int32_t cursorMoveSkip) override {}

  int32_t GetTextIndexAtCursor() override {
    return handler_->GetTextIndexAtCursor();
  }

  std::u16string GetLeftTextOfCursor(int32_t number) override {
    return handler_->GetLeftTextOfCursor(number);
  }

  std::u16string GetRightTextOfCursor(int32_t number) override {
    return handler_->GetRightTextOfCursor(number);
  }

  int32_t SetPreviewText(const std::u16string& text,
                         int32_t start,
                         int32_t end) override {
    return handler_->SetPreviewText(text, start, end);
  }

  void FinishTextPreview() override { return handler_->FinishTextPreview(); }

  void SetNeedUnderLine(bool is_need_underline) override {
    handler_->SetNeedUnderLine(is_need_underline);
  }

  void AutoFillWithIMFEvent(bool is_username,
                            bool is_other_account,
                            bool is_new_password,
                            const std::string& content) override {
    LOG(INFO) << "receive autofill event from IMF";
    handler_->AutoFillWithIMFEvent(is_username, is_other_account,
                                   is_new_password, content);
  }

 private:
  raw_ptr<NWebInputMethodHandler> handler_;
};

class InputMethodTask : public CefTask {
 public:
  explicit InputMethodTask(base::OnceClosure closure)
      : closure_(std::move(closure)) {}

  InputMethodTask(const InputMethodTask&) = delete;
  InputMethodTask& operator=(const InputMethodTask&) = delete;

  void Execute() override {
    std::move(closure_).Run();
  }

 private:
  base::OnceClosure closure_;

  IMPLEMENT_REFCOUNTING(InputMethodTask);
};

// LCOV_EXCL_START
NWebInputMethodHandler::NWebInputMethodHandler()
    : selected_from_(0), selected_to_(0), device_pixel_ratio_(0.0f) {
  inputmethod_adapter_ = OhosAdapterHelper::GetInstance().CreateIMFAdapter();
  if (inputmethod_adapter_ == nullptr) {
    LOG(ERROR) << "inputmethod_adapter_ create failed";
  }
}

NWebInputMethodHandler::~NWebInputMethodHandler() {}

uint32_t NWebInputMethodHandler::lastAttachNWebId_ = 0;
IMFAdapterTextInputType NWebInputMethodHandler::lastInputMode_ =
    IMFAdapterTextInputType::NONE;

std::shared_ptr<IMFCursorInfoAdapter> NWebInputMethodHandler::GetCursorInfo() {
  std::shared_ptr<NWebIMFCursorInfoAdapterImpl> cursorInfo =
      std::make_shared<NWebIMFCursorInfoAdapterImpl>();
  double left =
      (focus_rect_.x + focus_rect_.width) * device_pixel_ratio_ + offset_x_;
  double top = focus_rect_.y * device_pixel_ratio_ + offset_y_;
  double width = focus_rect_.width * device_pixel_ratio_;
  double height = focus_rect_.height * device_pixel_ratio_;

  cursorInfo->SetLeft(left);
  cursorInfo->SetTop(top);
  cursorInfo->SetWidth(width);
  cursorInfo->SetHeight(height);

  LOG(DEBUG) << "NWebInputMethodHandler::Attach cursorInfo.left = " << left
             << ", cursorInfo.top = " << top << ", cursorInfo.width = " << width
             << ", cursorInfo.height = " << height;
  return cursorInfo;
}
// LCOV_EXCL_STOP

IMFAdapterTextInputType NWebInputMethodHandler::TextInputModeToIMFAdapter(
    cef_text_input_mode_t mode) {
  switch (mode) {
    case CEF_TEXT_INPUT_MODE_TEXT:
      return IMFAdapterTextInputType::TEXT;
    case CEF_TEXT_INPUT_MODE_TEL:
      return IMFAdapterTextInputType::PHONE;
    case CEF_TEXT_INPUT_MODE_URL:
      return IMFAdapterTextInputType::URL;
    case CEF_TEXT_INPUT_MODE_EMAIL:
      return IMFAdapterTextInputType::EMAIL_ADDRESS;
    case CEF_TEXT_INPUT_MODE_NUMERIC:
    case CEF_TEXT_INPUT_MODE_DECIMAL:
      return IMFAdapterTextInputType::NUMBER;
    case CEF_TEXT_INPUT_MODE_SEARCH:
    default:
      type_text_flag_multi_line_ = true;
      return IMFAdapterTextInputType::TEXT;
  }
}

IMFAdapterTextInputType NWebInputMethodHandler::TextInputTypeToIMFAdapter(
    cef_text_input_type_t type) {
  switch (type) {
    case CEF_TEXT_INPUT_TYPE_TEXT:
      return IMFAdapterTextInputType::TEXT;
    case CEF_TEXT_INPUT_TYPE_PASSWORD:
      return IMFAdapterTextInputType::VISIBLE_PASSWORD;
    case CEF_TEXT_INPUT_TYPE_EMAIL:
      return IMFAdapterTextInputType::EMAIL_ADDRESS;
    case CEF_TEXT_INPUT_TYPE_NUMBER:
      return IMFAdapterTextInputType::NUMBER;
    case CEF_TEXT_INPUT_TYPE_TELEPHONE:
      return IMFAdapterTextInputType::PHONE;
    case CEF_TEXT_INPUT_TYPE_URL:
      return IMFAdapterTextInputType::URL;
    case CEF_TEXT_INPUT_TYPE_TEXT_AREA:
    case CEF_TEXT_INPUT_TYPE_CONTENT_EDITABLE:
      type_text_flag_multi_line_ = true;
      return IMFAdapterTextInputType::TEXT;
    default:
      return IMFAdapterTextInputType::TEXT;
  }
}

IMFAdapterEnterKeyType NWebInputMethodHandler::TextInputActionToIMFAdapter(
    InputInfo inputInfo) {
  if (inputInfo.input_action == CEF_TEXT_INPUT_ACTION_DEFAULT) {
    if (inputInfo.input_mode == CEF_TEXT_INPUT_MODE_DEFAULT &&
        inputInfo.input_type == CEF_TEXT_INPUT_TYPE_SEARCH) {
      LOG(DEBUG)
          << "The tag is not set with enterkeyhint. The type of the input box "
             "is search, so the enterkeytype is set to SEARCH.";
      return IMFAdapterEnterKeyType::SEARCH;
    } else if (type_text_flag_multi_line_) {
      LOG(DEBUG)
          << "The tag is not set with enterkeyhint. The input box supports "
             "multiple lines of input, so the enterkeytype is set to NEW_LINE.";
      return IMFAdapterEnterKeyType::NEW_LINE;
    } else if (inputInfo.input_flags &
               CEF_TEXT_INPUT_FLAG_HAVE_NEXT_FOCUSABLE_ELEMENT) {
      LOG(DEBUG)
          << "The tag is not set with enterkeyhint. There is the next edit box "
             "that can be focused on, so the enterkeytype is set to NEXT.";
      if (!base::ohos::IsPcDevice()) {
        return IMFAdapterEnterKeyType::NEXT;
      }
      return IMFAdapterEnterKeyType::GO;
    } else {
      LOG(DEBUG) << "The tag is not set with enterkeyhint. The above "
                    "situations are not met, so the enterkeytype is set to GO.";
      return IMFAdapterEnterKeyType::GO;
    }
  } else {
    switch (inputInfo.input_action) {
      case CEF_TEXT_INPUT_ACTION_DEFAULT:
        return IMFAdapterEnterKeyType::UNSPECIFIED;
      case CEF_TEXT_INPUT_ACTION_ENTER:
        LOG(DEBUG) << "Enterkeyhint is set to ENTER, therefore enterkeytype is "
                      "set to NEW_LINE.";
        return IMFAdapterEnterKeyType::NEW_LINE;
      case CEF_TEXT_INPUT_ACTION_DONE:
        LOG(DEBUG) << "Enterkeyhint is set to DONE, therefore enterkeytype is "
                      "set to DONE.";
        return IMFAdapterEnterKeyType::DONE;
      case CEF_TEXT_INPUT_ACTION_GO:
        LOG(DEBUG) << "Enterkeyhint is set to GO, therefore enterkeytype is "
                      "set to GO.";
        return IMFAdapterEnterKeyType::GO;
      case CEF_TEXT_INPUT_ACTION_NEXT:
        LOG(DEBUG) << "Enterkeyhint is set to NEXT, therefore enterkeytype is "
                      "set to NEXT.";
        return IMFAdapterEnterKeyType::NEXT;
      case CEF_TEXT_INPUT_ACTION_PREVIOUS:
        LOG(DEBUG) << "Enterkeyhint is set to PREVIOUS, therefore enterkeytype "
                      "is set to PREVIOUS.";
        return IMFAdapterEnterKeyType::PREVIOUS;
      case CEF_TEXT_INPUT_ACTION_SEARCH:
        LOG(DEBUG) << "Enterkeyhint is set to SEARCH, therefore enterkeytype "
                      "is set to SEARCH.";
        return IMFAdapterEnterKeyType::SEARCH;
      case CEF_TEXT_INPUT_ACTION_SEND:
        LOG(DEBUG) << "Enterkeyhint is set to SEND, therefore enterkeytype is "
                      "set to SEND.";
        return IMFAdapterEnterKeyType::SEND;
      default:
        return IMFAdapterEnterKeyType::GO;
    }
  }
}

// LCOV_EXCL_START
void NWebInputMethodHandler::HandleSecurityLayer() {
  if (browser_ == nullptr) {
    return;
  }

  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }

  CefRefPtr<CefTask> task = new InputMethodTask(base::BindOnce(
      &NWebInputMethodHandler::HandleSecurityLayerHandlerOnUI, this));
  host->PostTaskToUIThread(task);
}

void NWebInputMethodHandler::HandleSecurityLayerHandlerOnUI() {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  host->UpdateSecurityLayer(input_is_password_);
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::UpdateTextFieldStatus(bool isImeShowKeyboard, bool isTextInputfocus) {
  if (browser_ == nullptr) {
    return;
  }

  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }

  CefRefPtr<CefTask> task = new InputMethodTask(base::BindOnce(
      &NWebInputMethodHandler::UpdateTextFieldStatusHandlerOnUI, this, isImeShowKeyboard, isTextInputfocus));
  host->PostTaskToUIThread(task);
}

void NWebInputMethodHandler::UpdateTextFieldStatusHandlerOnUI(bool isImeShowKeyboard, bool isTextInputfocus) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  host->UpdateTextFieldStatus(isImeShowKeyboard, isTextInputfocus);
}

void NWebInputMethodHandler::ComputeEditorInfo(InputInfo inputInfo,
                                               int32_t customEnterKeyType) {
  type_text_flag_multi_line_ = false;
  show_keyboard_ = inputInfo.show_keyboard;
  input_flags_ = inputInfo.input_flags;
  input_node_id_ = inputInfo.node_id;
  cef_text_input_mode_ = inputInfo.input_mode;
  input_is_password_ = (inputInfo.input_type == CEF_TEXT_INPUT_TYPE_PASSWORD);
  if (inputInfo.input_mode != CEF_TEXT_INPUT_MODE_DEFAULT &&
      inputInfo.input_type != CEF_TEXT_INPUT_TYPE_PASSWORD) {
    imf_input_mode_ = TextInputModeToIMFAdapter(inputInfo.input_mode);
  } else {
    imf_input_mode_ = TextInputTypeToIMFAdapter(inputInfo.input_type);
  }
  imf_input_action_ = static_cast<IMFAdapterEnterKeyType>(customEnterKeyType);
  if (customEnterKeyType < 0 || customEnterKeyType > MAX_ENTERKEYTYPE) {
    imf_input_action_ = TextInputActionToIMFAdapter(inputInfo);
  }
}

bool NWebInputMethodHandler::AttachToSystemIME(bool is_need_reset_listener, int32_t requestKeyboardReason) {
  if (inputmethod_adapter_ == nullptr) {
    LOG(ERROR) << "inputmethod_adapter_ is nullptr";
    return false;
  }
  if (!focus_status_) {
    LOG(INFO) << "Do not attach the IME in the unfocused status, will reattach "
                 "after focused";
    isNeedReattachOnfocus_ = true;
    return false;
  }
  if (inputmethod_listener_ == nullptr) {
    inputmethod_listener_ = std::make_shared<OnTextChangedListenerImpl>(this);
  }

  std::shared_ptr<NWebIMFInputAttributeAdapterImpl> inputAttribute =
      std::make_shared<NWebIMFInputAttributeAdapterImpl>();
  inputAttribute->SetInputPattern(static_cast<int32_t>(imf_input_mode_));
  inputAttribute->SetEnterKeyType(static_cast<int32_t>(imf_input_action_));

  std::shared_ptr<IMFCursorInfoAdapter> cursorInfo = GetCursorInfo();

  std::shared_ptr<NWebIMFTextConfigAdapterImpl> textConfig =
      std::make_shared<NWebIMFTextConfigAdapterImpl>();
  textConfig->SetInputAttribute(inputAttribute);
  textConfig->SetCursorInfo(cursorInfo);
  textConfig->SetWindowId(windowId_);
  textConfig->SetPositionY(offset_y_);
  textConfig->SetHeight((focus_rect_.y + focus_rect_.height + AVOID_OFFSET) *
                        device_pixel_ratio_);

  bool show_keyboard = NeedKeyboardShow();
  isAttachSuccess_ = inputmethod_adapter_->AttachWithRequestKeyboardReason(
      inputmethod_listener_, show_keyboard, textConfig, is_need_reset_listener,
      requestKeyboardReason);
  if (ArkWebGetErrno() != ArkWebInterfaceResult::RESULT_OK) {
    isAttachSuccess_ = inputmethod_adapter_->Attach(inputmethod_listener_, show_keyboard,
                                                    textConfig, is_need_reset_listener);
  }
  if (!isAttachSuccess_) {
    LOG(ERROR) << "inputmethod_adapter_ attach failed";
    return false;
  }
  isFocusSwitchOnBlur_ = false;

#if BUILDFLAG(ARKWEB_PASSWORD_AUTOFILL)
  if (!fill_content_.empty()) {
    if (fill_content_node_id_ == input_node_id_) {
      LOG(INFO) << "send autofill cancel fill content to IMF";
      inputmethod_adapter_->SendPrivateCommand(AUTO_FILL_CANCEL_PRIVATE_COMMAND,
                                               fill_content_);
    }
    fill_content_.clear();
  }
#endif
  return true;
}

// LCOV_EXCL_START
bool NWebInputMethodHandler::NeedKeyboardShow() {
  // if the keyboard is closed manually, the keyboard will not show automatically.
  // if the inputmode is none, the system keyboard will not show.
  return show_keyboard_ && !isManualCloseKeyboard_ &&
         (cef_text_input_mode_ != CEF_TEXT_INPUT_MODE_NONE);
}

bool NWebInputMethodHandler::IsKeyboardShow() {
  // If the inputmethod is attached successfully and the keyboard is needed, then the
  // keyboard is shown.
  return isAttachSuccess_ && NeedKeyboardShow();
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::Attach(CefRefPtr<CefBrowser> browser,
                                    InputInfo inputInfo,
                                    bool is_need_reset_listener,
                                    int32_t enterKeyType) {
  LOG(INFO) << "NWebInputMethodHandler::Attach";
  int32_t requestKeyboardReasonNone = 0;
  Attach(browser, inputInfo, is_need_reset_listener, enterKeyType, requestKeyboardReasonNone);
}

void NWebInputMethodHandler::Attach(CefRefPtr<CefBrowser> browser,
                                    InputInfo inputInfo,
                                    bool is_need_reset_listener,
                                    int32_t enterKeyType,
                                    int32_t requestKeyboardReason) {
  LOG(INFO) << "NWebInputMethodHandler::Attach";
  ComputeEditorInfo(inputInfo, enterKeyType);
  composing_text_.clear();
  browser_ = browser;
  isManualCloseKeyboard_ = false;

  if (!AttachToSystemIME(is_need_reset_listener, requestKeyboardReason)) {
    return;
  }
  isAttached_ = true;
  lastAttachNWebId_ = nweb_id_;
  lastInputMode_ = imf_input_mode_;

  if (focus_status_ && focus_rect_status_) {
    if (inputmethod_adapter_) {
      inputmethod_adapter_->OnCursorUpdate(GetCursorInfo());
    }
  }
}

bool NWebInputMethodHandler::Reattach(uint32_t nwebId, ReattachType type) {
  nweb_id_ = nwebId;
  bool textInputState = true;
  if (browser_ && browser_->GetHost()) {
    textInputState = browser_->GetHost()->JudgeTextInputState();
  }
  if (type == ReattachType::FROM_CONTINUE) {
    if (!isNeedReattachOncontinue_ || !is_editable_node_) {
      LOG(INFO) << "don't need reattach input method " << textInputState;
      if (textInputState) {
        return false;
      }
    }
    isNeedReattachOncontinue_ = false;
  }

  if (type == ReattachType::FROM_ONFOCUS) {
    if (!isNeedReattachOnfocus_ || !is_editable_node_) {
      LOG(INFO) << "ReAttchOnfocus, don't need reattach input method";
      return false;
    }
    isNeedReattachOnfocus_ = false;
  }

  if (type == ReattachType::FROM_ONDRAG) {
    if (!is_editable_node_) {
      LOG(INFO) << "ReAttchOnDrag, don't need reattach input method for non-editable node.";
      return false;
    }
    LOG(INFO) << "Trigger reattach, nwebId=" << nwebId << ", source=FROM_ONDRAG";
  } else {
    LOG(INFO) << "Trigger reattach, nwebId=" << nwebId << ", source="
              << (type == ReattachType::FROM_ONFOCUS ? "focus" : "continue");
  }

  composing_text_.clear();
  ClearComposingStatus();
  if (!show_keyboard_ && isAttached_ && imf_input_mode_ != lastInputMode_) {
    LOG(ERROR) << "do not need attach";
    inputmethod_adapter_->Close();
  }

  if (!AttachToSystemIME(false)) {
    return false;
  }

  HandleSecurityLayer();
  isAttached_ = true;
  lastAttachNWebId_ = nwebId;
  lastInputMode_ = imf_input_mode_;

  return show_keyboard_;
}

void NWebInputMethodHandler::ShowTextInput() {
  LOG(INFO) << "NWebInputMethodHandler::ShowTextInput";
}

void NWebInputMethodHandler::SetNeedReattach(HideTextinputType hideType) {
  if (hideType == HideTextinputType::FROM_ONPAUSE) {
    isNeedReattachOncontinue_ = true;
  }
  if (hideType == HideTextinputType::FROM_ONBLUR) {
    isNeedReattachOnfocus_ = true;
  }
}

void NWebInputMethodHandler::HideTextInput(uint32_t nwebId,
                                           HideTextinputType hideType,
                                           bool noNeedKeyboardByInput) {
  LOG(INFO) << "NWebInputMethodHandler::HideTextInput, isAttached_: "
            << isAttached_;
  ClearComposingStatus();
  if (inputmethod_adapter_ == nullptr) {
    LOG(ERROR) << "inputmethod_adapter_ is nullptr";
    return;
  }
  if (!isAttached_) {
    // when there is an attach failure occured, inputmethod need to be attached again.
    if (!isAttachSuccess_) {
      SetNeedReattach(hideType);
      LOG(INFO) << "HideTextInput is triggered after an attach failure, "
                   "need to reattach next time.";
    }
    if (noNeedKeyboardByInput) {
      LOG(INFO) << "inputMode is None, no need keyboard.";
      inputmethod_adapter_->HideTextInput();
      return;
    }
    if (hideType != HideTextinputType::FROM_ONPAUSE) {
      LOG(INFO) << "not from switch front and background, ingnore";
      return;
    }
    auto nowTime = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> diff =
        std::chrono::duration_cast<std::chrono::milliseconds>(
            nowTime - lastCloseInputMethodTime_);
    // 100: received another hide textinput event in 100ms, we set
    // isNeedReattachOncontinue_ flag.
    if (diff.count() < 100) {
      isNeedReattachOncontinue_ = true;
      LOG(INFO) << "set isNeedReattachOncontinue_ flag, diff = " << diff.count()
                << "ms";
    }
    return;
  }
  if (isFocusSwitchOnBlur_) {
    LOG(INFO) << "triggered by focus switch, inputmethod is not attached, do not need hidetextinput";
    return;
  }

  if (lastAttachNWebId_ == 0 || lastAttachNWebId_ == nwebId ||
      hideType == HideTextinputType::FROM_KERNEL) {
    LOG(INFO) << "need to hide text input, hidetype = "
              << static_cast<int>(hideType);
    inputmethod_adapter_->HideTextInput();
    inputmethod_adapter_->Close();
  }

  lastCloseInputMethodTime_ = std::chrono::high_resolution_clock::now();
  isAttached_ = false;
  SetNeedReattach(hideType);
}

// LCOV_EXCL_START
void NWebInputMethodHandler::HideTextInputForce() {
  LOG(INFO) << "NWebInputMethodHandler::HideTextInputForce";
  if (inputmethod_adapter_ == nullptr) {
    LOG(ERROR) << "inputmethod_adapter_ is nullptr";
    return;
  }

  inputmethod_adapter_->HideTextInput();
  inputmethod_adapter_->Close();
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::OnTextSelectionChanged(
    CefRefPtr<CefBrowser> browser,
    const CefString& selected_text,
    const CefRange& selected_range) {
  selected_text_ = selected_text.ToString16();
}

void NWebInputMethodHandler::OnCursorUpdate(const CefRect& rect) {
  focus_rect_ = rect;
  focus_rect_status_ = true;
  if (focus_status_) {
    std::shared_ptr<NWebIMFCursorInfoAdapterImpl> cursorInfo =
        std::make_shared<NWebIMFCursorInfoAdapterImpl>();
    double left = (rect.x + rect.width) * device_pixel_ratio_ + offset_x_;
    double top = rect.y * device_pixel_ratio_ + offset_y_;
    double width = rect.width * device_pixel_ratio_;
    double height = rect.height * device_pixel_ratio_;

    cursorInfo->SetLeft(left);
    cursorInfo->SetTop(top);
    cursorInfo->SetWidth(width);
    cursorInfo->SetHeight(height);

    LOG(DEBUG) << "NWebInputMethodHandler::OnCursorUpdate cursorInfo.left = "
               << left << ", cursorInfo.top = " << top
               << ", cursorInfo.width = " << width
               << ", cursorInfo.height = " << height;
    if (inputmethod_adapter_) {
      inputmethod_adapter_->OnCursorUpdate(cursorInfo);
    }
  }
}

void NWebInputMethodHandler::OnSelectionChanged(
    CefRefPtr<CefBrowser> browser,
    const CefString& text,
    const CefRange& selected_range) {
  LOG(DEBUG) << "NWebInputMethodHandler::OnSelectionChanged";
}

void NWebInputMethodHandler::OnImeCompositionRangeChanged(
    CefRefPtr<CefBrowser> browser,
    const CefRange& selected_range) {
  LOG(DEBUG) << "NWebInputMethodHandler::OnImeCompositionRangeChanged"
             << selected_range.from << ", to: " << selected_range.to;
}

bool NWebInputMethodHandler::IsTextInputStateChange(
    const CefString& text,
    const CefRange& selected_range,
    const CefRange& compositon_range) {
  if (CefString(whole_text_) != text) {
    return true;
  }

  if ((selected_range.from != static_cast<uint32_t>(selected_from_)) ||
      (selected_range.to != static_cast<uint32_t>(selected_to_))) {
    return true;
  }

  if ((compositon_range.from !=
       static_cast<uint32_t>(composition_range_start_)) ||
      (compositon_range.to != static_cast<uint32_t>(composition_range_end_))) {
    return true;
  }

  return false;
}

void NWebInputMethodHandler::OnUpdateTextInputStateCalled(
    CefRefPtr<CefBrowser> browser,
    const CefString& text,
    const CefRange& selected_range,
    const CefRange& compositon_range) {
  bool is_state_change =
      IsTextInputStateChange(text, selected_range, compositon_range);
  if (!is_state_change) {
    LOG(DEBUG) << "NWebInputMethodHandler::OnUpdateTextInputStateCalled state "
                  "is not changed";
    return;
  }

  LOG(DEBUG)
      << "NWebInputMethodHandler::OnUpdateTextInputStateCalled text_length = "
      << text.ToString16().length()
      << ", selected_range start = " << selected_range.from
      << ", to = " << selected_range.to
      << ", compositon_range start = " << compositon_range.from
      << ", to = " << compositon_range.to;
  if (compositon_range == CefRange::InvalidRange()) {
    has_composition_ = false;
    if (browser_ && browser_->GetHost()) {
      browser_->GetHost()->SetHasComposition(has_composition_);
    }
    composition_range_start_ = 0;
    composition_range_end_ = 0;
    preview_text_cache_ = u"";
  } else {
    has_composition_ = true;
    if (browser_ && browser_->GetHost()) {
      browser_->GetHost()->SetHasComposition(has_composition_);
    }
    composition_range_start_ =
        (compositon_range.from > static_cast<uint32_t>(INT32_MAX))
            ? INT32_MAX
            : static_cast<int32_t>(compositon_range.from);
    composition_range_end_ = compositon_range.to;
    int32_t preview_length = composition_range_end_ - composition_range_start_;
    if (composition_range_start_ >= 0 && preview_length > 0) {
      const auto& text16 = text.ToString16();
      size_t start = static_cast<size_t>(composition_range_start_);
      size_t length = static_cast<size_t>(preview_length);
      if (!text16.empty() && (start + length <= text16.length())) {
        preview_text_cache_ = text16.substr(start, length);
      }
    }
  }
  LOG(DEBUG) << "NWebInputMethodHandler::OnUpdateTextInputStateCalled";

  selected_from_ = selected_range.from;
  selected_to_ = selected_range.to;

  if (inputmethod_adapter_) {
    inputmethod_adapter_->OnSelectionChange(text.ToString16(), selected_from_,
                                            selected_to_);
  }

  {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    whole_text_ = text.ToString16();
    textCursorReady_ = 0;
    textCursorCv_.notify_all();
  }
}

void NWebInputMethodHandler::SetIMEStatus(bool status) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> insert_task = new InputMethodTask(base::BindOnce(
      &NWebInputMethodHandler::SetIMEStatusOnUI, this, std::move(status)));
  host->PostTaskToUIThread(insert_task);
}

// LCOV_EXCL_START
void NWebInputMethodHandler::WebBlurKeyboardHide() {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> task = new InputMethodTask(
      base::BindOnce(&NWebInputMethodHandler::WebBlurKeyboardHideOnUI, this));
  host->PostTaskToUIThread(task);
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::WebSetImeShow(bool visible) {
  if (browser_ != nullptr && browser_->GetHost() != nullptr) {
    browser_->GetHost()->SetImeShow(visible);
  }
}

void NWebInputMethodHandler::InsertText(const std::u16string& text) {
  if (text.empty()) {
    LOG(ERROR) << "insert text empty!";
    return;
  }

  if (browser_ != nullptr && browser_->GetHost() != nullptr) {
    CefRefPtr<CefTask> insert_task = new InputMethodTask(base::BindOnce(
        &NWebInputMethodHandler::InsertTextHandlerOnUI, this, std::move(text)));
    browser_->GetHost()->PostTaskToUIThread(insert_task);
  }
}

void NWebInputMethodHandler::DeleteBackward(int32_t length) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> delete_task = new InputMethodTask(base::BindOnce(
      &NWebInputMethodHandler::DeleteBackwardHandlerOnUI, this, length));
  host->PostTaskToUIThread(delete_task);
}

void NWebInputMethodHandler::DeleteForward(int32_t length) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> delete_task = new InputMethodTask(base::BindOnce(
      &NWebInputMethodHandler::DeleteForwardHandlerOnUI, this, length));
  host->PostTaskToUIThread(delete_task);
}

void NWebInputMethodHandler::SetIMEStatusOnUI(bool status) {
  LOG(INFO) << "NWebInputMethodHandler::SetIMEStatusOnUI status:" << status;
  if (!status && ime_text_composing_) {
    if (browser_->GetHost()) {
      browser_->GetHost()->ImeFinishComposingText(false);
    }
    ime_text_composing_ = false;
    composing_text_.clear();
  }
  if (status) {
    isManualCloseKeyboard_ = false;
  }
  ime_shown_ = status;
  UpdateTextFieldStatus(status, status);
}

// LCOV_EXCL_START
void NWebInputMethodHandler::WebBlurKeyboardHideOnUI() {
  LOG(INFO) << "NWebInputMethodHandler::WebBlurKeyboardHideOnUI";
  isManualCloseKeyboard_ = true;
  if (browser_->GetHost()) {
    browser_->GetHost()->SetFocusOnWeb();
  }
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::InsertTextHandlerOnUI(const std::u16string& text) {
  LOG(INFO) << "NWebInputMethodHandler::InsertTextHandlerOnUI text length:" << text.length();
  if (text.empty()) {
    LOG(ERROR) << "insert text empty!";
    return;
  }
  {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    textCursorReady_++;
  }

  if (browser_ == nullptr) {
    LOG(ERROR) << "browser is null in InsertTextHandlerOnUI";
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    LOG(ERROR) << "browser host is null in InsertTextHandlerOnUI";
    return;
  }
  CefKeyEvent keyEvent;
  keyEvent.windows_key_code = ui::VKEY_PROCESSKEY;

  keyEvent.modifiers = 0;
  keyEvent.is_system_key = false;
  keyEvent.type = KEYEVENT_RAWKEYDOWN;
  host->SendKeyEvent(keyEvent);

  if (!ime_text_composing_) {
    ime_text_composing_ = true;
    composing_text_.clear();
  }
  composing_text_.append(text);
  host->ImeCommitText(composing_text_, CefRange(UINT32_MAX, UINT32_MAX),
                      0);

  if (text.length() > 1) {
    ResSchedClientAdapter::ReportScene(ResSchedStatusAdapter::WEB_SCENE_ENTER,
                                       ResSchedSceneAdapter::CLICK);
  }

  // no selection
  ime_text_composing_ = false;
  composing_text_.clear();
  LOG(DEBUG) << "NWebInputMethodHandler::InsertTextHandlerOnUI selected_from_ "
             << selected_from_;
  keyEvent.type = KEYEVENT_KEYUP;
  host->SendKeyEvent(keyEvent);
  ClearComposingStatus();
}

// LCOV_EXCL_START
void NWebInputMethodHandler::ClearComposingStatus() {
  has_composition_ = false;
  if (browser_ != nullptr) {
    CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
    if (host != nullptr) {
      host->SetHasComposition(has_composition_);
    }
  }
  preview_text_cache_ = u"";
  composition_range_start_ = 0;
  composition_range_end_ = 0;
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::PreviewTextHandlerOnUI(const std::u16string& text,
                                                    int32_t start,
                                                    int32_t end) {
  LOG(DEBUG) << "NWebInputMethodHandler start " << start << ", end " << end;
  {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    textCursorReady_++;
  }

  LOG(DEBUG) << "NWebInputMethodHandler::preview_text_cache_ After "
                "composition_range_start_ "
             << composition_range_start_ << ", composition_range_end_ "
             << composition_range_end_;
  std::vector<CefCompositionUnderline> underlines;
  CefCompositionUnderline underline;
  underline.range.from = 0;
  underline.range.to = text.length();
  if (is_need_underline_) {
    underline.style = CEF_CUS_SOLID;
  } else {
    underline.style = CEF_CUS_NONE;
  }
  underlines.push_back(underline);

  CefRange replace_range = CefRange::InvalidRange();
  if (composition_type_ == COMPOSITION_REPLACE) {
    replace_range.Set(start, end);
  }
  CefRange selection_range(composition_cursor_index_,
                           composition_cursor_index_);
  LOG(DEBUG) << "NWebInputMethodHandler::underline style: " << underline.style
             << ", from: " << underline.range.from
             << ", to: " << underline.range.to;
  LOG(DEBUG) << "NWebInputMethodHandler::replace_range from "
             << replace_range.from << ", to " << replace_range.to;
  LOG(DEBUG) << "NWebInputMethodHandler::selection_range from "
             << selection_range.from << ", to " << selection_range.to;

  if (browser_ != nullptr) {
    CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
    if (host != nullptr) {
      host->ImeSetComposition(text, underlines, replace_range, selection_range);
    }
  }
}

// LCOV_EXCL_START
void NWebInputMethodHandler::CancelPreviewHandlerOnUI() {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  LOG(DEBUG) << "NWebInputMethodHandler::CancelPreviewHandlerOnUI";
  host->ImeCancelComposition();
  ClearComposingStatus();
}

void NWebInputMethodHandler::FinishPreviewTextOnUI() {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  LOG(DEBUG) << "NWebInputMethodHandler::FinishPreviewTextOnUI";
  host->ImeFinishComposingText(false);
  ClearComposingStatus();
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::SetNeedUnderLineOnUI(bool is_need_underline) {
  LOG(DEBUG) << "NWebInputMethodHandler::SetNeedUnderLine "
             << is_need_underline;
  is_need_underline_ = is_need_underline;
}

void NWebInputMethodHandler::DeleteForwardHandlerOnUI(int32_t length) {
  CefKeyEvent keyEvent;
  keyEvent.windows_key_code = ui::VKEY_DELETE;
  keyEvent.native_key_code = static_cast<int>(ScanKeyCode::DELETE_SCAN_CODE);
  keyEvent.modifiers = 0;
  keyEvent.is_system_key = false;
  keyEvent.character = keyEvent.unmodified_character = DEL_CHAR;
  LOG(DEBUG) << "NWebInputMethodHandler::DeleteForwardHandlerOnUI";

  if (browser_ == nullptr) {
    LOG(ERROR) << "delete backward browser get failed";
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    LOG(ERROR) << "delete backward browser host get failed";
    return;
  }
  is_need_notify_all_ = false;
  text_cursor_length_ = length;
  int size = static_cast<int>(whole_text_.size());
  selected_from_ = selected_from_ >= size ? size : selected_from_;
  LOG(DEBUG)
      << "NWebInputMethodHandler::DeleteForwardHandlerOnUI selected_from_ "
      << selected_from_;
  if (static_cast<int32_t>(whole_text_.substr(selected_from_).size()) <= length) {
    text_cursor_length_ =
        static_cast<int32_t>(whole_text_.substr(selected_from_).size());
    if (selected_from_ == 0) {
      is_need_notify_all_ = true;
    }
  } else {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    textCursorReady_ += text_cursor_length_;
  }
  for (int32_t i = 0; i < length; i++) {
    keyEvent.type = KEYEVENT_RAWKEYDOWN;
    host->SendKeyEvent(keyEvent);
    keyEvent.type = KEYEVENT_CHAR;
    host->SendKeyEvent(keyEvent);
  }
  keyEvent.type = KEYEVENT_KEYUP;
  host->SendKeyEvent(keyEvent);
  if (preview_text_cache_.length() == 1) {
    ClearComposingStatus();
  }
}

void NWebInputMethodHandler::DeleteBackwardHandlerOnUI(int32_t length) {
  CefKeyEvent keyEvent;
  keyEvent.windows_key_code = ui::VKEY_BACK;
  keyEvent.native_key_code = static_cast<int>(ScanKeyCode::BACKSPACE_SCAN_CODE);
  keyEvent.modifiers = 0;
  keyEvent.is_system_key = false;
  keyEvent.character = keyEvent.unmodified_character = DEL_CHAR;
  LOG(DEBUG) << "NWebInputMethodHandler::DeleteBackwardHandlerOnUI";

  if (browser_ == nullptr) {
    LOG(ERROR) << "delete backward browser get failed";
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    LOG(ERROR) << "delete backward browser host get failed";
    return;
  }
  text_cursor_length_ = length;
  if (selected_from_ <= length) {
    text_cursor_length_ = selected_from_;
  } else {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    textCursorReady_ += text_cursor_length_;
  }
  for (int32_t i = 0; i < length; i++) {
    keyEvent.type = KEYEVENT_RAWKEYDOWN;
    host->SendKeyEvent(keyEvent);
    keyEvent.type = KEYEVENT_CHAR;
    host->SendKeyEvent(keyEvent);
  }
  keyEvent.type = KEYEVENT_KEYUP;
  host->SendKeyEvent(keyEvent);
  if (preview_text_cache_.length() == 1) {
    ClearComposingStatus();
  }
}

void NWebInputMethodHandler::SendEnterKeyEvent(int32_t enterKeyType) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> sendEnterKeyEvent_task = new InputMethodTask(
      base::BindOnce(&NWebInputMethodHandler::SendEnterKeyEventOnUI, this,
                     std::move(enterKeyType)));
  host->PostTaskToUIThread(sendEnterKeyEvent_task);
}

void NWebInputMethodHandler::SendEnterKeyEventOnUI(int32_t enterKeyType) {
  if (browser_ == nullptr) {
    LOG(ERROR)
        << "NWebInputMethodHandler send enter key failed, browser_ is nullptr!";
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    LOG(ERROR) << "NWebInputMethodHandler send enter key failed, browser host "
                  "is nullptr!";
    return;
  }
  if (!base::ohos::IsPcDevice()) {
    if (enterKeyType == static_cast<int32_t>(IMFAdapterEnterKeyType::NEXT) &&
        (static_cast<uint32_t>(input_flags_) &
         static_cast<uint32_t>(
             CEF_TEXT_INPUT_FLAG_HAVE_NEXT_FOCUSABLE_ELEMENT))) {
      LOG(DEBUG) << "NWebInputMethodHandler::SendEnterKeyEvent "
                    "IMFAdapterEnterKeyType::NEXT";
      host->AdvanceFocusForIME(static_cast<int>(FocusType::FORWARD));
      return;
    } else if (enterKeyType ==
                   static_cast<int32_t>(IMFAdapterEnterKeyType::PREVIOUS) &&
               (static_cast<uint32_t>(input_flags_) &
                static_cast<uint32_t>(
                    CEF_TEXT_INPUT_FLAG_HAVE_PREVIOUS_FOCUSABLE_ELEMENT))) {
      LOG(DEBUG) << "NWebInputMethodHandler::SendEnterKeyEvent "
                    "IMFAdapterEnterKeyType::PREVIOUS";
      host->AdvanceFocusForIME(static_cast<int>(FocusType::BACKWARD));
      return;
    }
  }

  CefKeyEvent keyEvent;
  keyEvent.windows_key_code = ui::VKEY_RETURN;
  keyEvent.native_key_code = static_cast<int>(ScanKeyCode::ENTER_SCAN_CODE);
  LOG(DEBUG) << "NWebInputMethodHandler::SendEnterKeyEvent";

  keyEvent.type = KEYEVENT_KEYDOWN;
  keyEvent.character = '\r';
  keyEvent.modifiers = 0;
  keyEvent.is_system_key = false;
  browser_->GetHost()->SendKeyEvent(keyEvent);

  keyEvent.type = KEYEVENT_CHAR;
  browser_->GetHost()->SendKeyEvent(keyEvent);

  keyEvent.type = KEYEVENT_KEYUP;
  browser_->GetHost()->SendKeyEvent(keyEvent);
}

void NWebInputMethodHandler::MoveCursor(const IMFAdapterDirection direction) {
  LOG(INFO) << "NWebInputMethodHandler::MoveCursor called";
  CefKeyEvent keyEvent;

  switch (direction) {
    case IMFAdapterDirection::UP: {
      keyEvent.windows_key_code = ui::VKEY_UP;
      keyEvent.native_key_code = static_cast<int>(ScanKeyCode::UP_SCAN_CODE);
      break;
    }
    case IMFAdapterDirection::LEFT: {
      keyEvent.windows_key_code = ui::VKEY_LEFT;
      keyEvent.native_key_code = static_cast<int>(ScanKeyCode::LEFT_SCAN_CODE);
      break;
    }
    case IMFAdapterDirection::RIGHT: {
      keyEvent.windows_key_code = ui::VKEY_RIGHT;
      keyEvent.native_key_code = static_cast<int>(ScanKeyCode::RIGHT_SCAN_CODE);
      break;
    }
    case IMFAdapterDirection::DOWN: {
      keyEvent.windows_key_code = ui::VKEY_DOWN;
      keyEvent.native_key_code = static_cast<int>(ScanKeyCode::DOWN_SCAN_CODE);
      break;
    }
    default: {
      LOG(ERROR) << "invalid direction";
      return;
    }
  }
  {
    std::unique_lock<std::mutex> lock(textCursorMutex_);
    textCursorReady_++;
  }
  keyEvent.type = KEYEVENT_KEYDOWN;
  keyEvent.modifiers = 0;
  keyEvent.is_system_key = false;
  if (browser_ && browser_->GetHost()) {
    browser_->GetHost()->SendKeyEvent(keyEvent);
  }

  keyEvent.type = KEYEVENT_KEYUP;
  if (browser_ && browser_->GetHost()) {
    browser_->GetHost()->SendKeyEvent(keyEvent);
  }
}

// LCOV_EXCL_START
void NWebInputMethodHandler::SetScreenOffSet(double x, double y) {
  if (focus_status_) {
    if (focus_rect_status_ && (offset_x_ != x || offset_y_ != y)) {
      std::shared_ptr<NWebIMFCursorInfoAdapterImpl> cursorInfo =
          std::make_shared<NWebIMFCursorInfoAdapterImpl>();
      double left =
          (focus_rect_.x + focus_rect_.width) * device_pixel_ratio_ + x;
      double top = focus_rect_.y * device_pixel_ratio_ + y;
      double width = focus_rect_.width * device_pixel_ratio_;
      double height = focus_rect_.height * device_pixel_ratio_;

      cursorInfo->SetLeft(left);
      cursorInfo->SetTop(top);
      cursorInfo->SetWidth(width);
      cursorInfo->SetHeight(height);

      LOG(DEBUG) << "NWebInputMethodHandler::SetScreenOffSet cursorInfo.left = "
                 << left << ", cursorInfo.top = " << top
                 << ", cursorInfo.width = " << width
                 << ", cursorInfo.height = " << height;
      if (inputmethod_adapter_) {
        inputmethod_adapter_->OnCursorUpdate(cursorInfo);
      }
    }
  }
  offset_x_ = x;
  offset_y_ = y;
}

void NWebInputMethodHandler::SetVirtualDeviceRatio(float device_pixel_ratio) {
  device_pixel_ratio_ = device_pixel_ratio;
}

void NWebInputMethodHandler::SetFocusStatus(bool focus_status) {
  if (focus_status && focus_rect_status_) {
    std::shared_ptr<NWebIMFCursorInfoAdapterImpl> cursorInfo =
        std::make_shared<NWebIMFCursorInfoAdapterImpl>();
    double left =
        (focus_rect_.x + focus_rect_.width) * device_pixel_ratio_ + offset_x_;
    double top = focus_rect_.y * device_pixel_ratio_ + offset_y_;
    double width = focus_rect_.width * device_pixel_ratio_;
    double height = focus_rect_.height * device_pixel_ratio_;

    cursorInfo->SetLeft(left);
    cursorInfo->SetTop(top);
    cursorInfo->SetWidth(width);
    cursorInfo->SetHeight(height);
    LOG(DEBUG) << "NWebInputMethodHandler::SetFocusStatus cursorInfo.left = "
               << left << ", cursorInfo.top = " << top << ", width = " << width
               << ", height = " << height;
    if (inputmethod_adapter_) {
      inputmethod_adapter_->OnCursorUpdate(cursorInfo);
    }
  }
  focus_status_ = focus_status;
}

void NWebInputMethodHandler::OnEditableChanged(CefRefPtr<CefBrowser> browser,
                                               bool is_editable_node) {
  is_editable_node_ = is_editable_node;
}

bool NWebInputMethodHandler::GetIsEditableNode() {
  LOG(INFO) << "NWebInputMethodHandler is_editable_node_ = "
            << is_editable_node_;
  return is_editable_node_;
}

bool NWebInputMethodHandler::HasComposition() {
  LOG(INFO) << "NWebInputMethodHandler HasComposition = " << has_composition_;
  return has_composition_;
}
int32_t NWebInputMethodHandler::GetTextIndexAtCursor() {
  std::unique_lock<std::mutex> lock(textCursorMutex_);
  bool istextCursorReady =
      textCursorCv_.wait_for(lock, std::chrono::milliseconds(50),
                             [this] { return textCursorReady_ == 0; });
  textCursorReady_ = 0;
  if (!istextCursorReady) {
    LOG(ERROR) << "GetTextIndexAtCursor wait_for timeout";
    if (ResetTextSelectiondata()) {
      return 0;
    }
  }
  LOG(DEBUG) << "NWebInputMethodHandler::GetTextIndexAtCursor selected_to_ "
             << selected_to_;
  return selected_to_;
}
// LCOV_EXCL_STOP

std::u16string NWebInputMethodHandler::GetLeftTextOfCursor(int32_t number) {
  std::unique_lock<std::mutex> lock(textCursorMutex_);
  bool istextCursorReady =
      textCursorCv_.wait_for(lock, std::chrono::milliseconds(50),
                             [this] { return textCursorReady_ == 0; });
  textCursorReady_ = 0;
  if (!istextCursorReady) {
    LOG(ERROR) << "GetLeftTextOfCursor wait_for timeout";
    if (ResetTextSelectiondata()) {
      return u"";
    }
  }
  int32_t selectBegin = selected_from_;
  int32_t selectEnd = selected_to_;
  if (!IsCorrectParam(number, selectBegin, selectEnd)) {
    return u"";
  }

  int32_t startPos = (number <= selectBegin ? (selectBegin - number) : 0);
  int32_t length = (number <= selectBegin ? number : selectBegin);
  return whole_text_.substr(startPos, length);
}

std::u16string NWebInputMethodHandler::GetRightTextOfCursor(int32_t number) {
  std::unique_lock<std::mutex> lock(textCursorMutex_);

  bool istextCursorReady =
      textCursorCv_.wait_for(lock, std::chrono::milliseconds(50),
                             [this] { return textCursorReady_ == 0; });
  textCursorReady_ = 0;

  if (!istextCursorReady) {
    LOG(ERROR) << "GetRightTextOfCursor wait_for timeout";
    if (ResetTextSelectiondata()) {
      return u"";
    }
  }
  int32_t selectBegin = selected_from_;
  int32_t selectEnd = selected_to_;
  if (!IsCorrectParam(number, selectBegin, selectEnd)) {
    return u"";
  }
  return whole_text_.substr(selectEnd, number);
}

int32_t NWebInputMethodHandler::GetCompositionTypeAndCheckInput(
    const std::u16string& text,
    int32_t start,
    int32_t end,
    CompositionType& composition_type) {
  bool is_empty = text.empty();
  if (is_empty && !has_composition_) {
    LOG(ERROR) << "set null preview text when has not composition";
    composition_type = COMPOSITION_INVALID;
    return ERROR;
  }

  CompositionType type;
  if ((start == -1) && (end == -1)) {
    if (is_empty) {
      type = COMPOSITION_CANCEL;
    } else {
      type = COMPOSITION_CURRENT;
    }
  } else if (start == end) {
    type = COMPOSITION_POSITION;
  } else if (start < end) {
    if (is_empty && (preview_text_cache_.length() == 1)) {
      type = COMPOSITION_DELETE;
    } else {
      type = COMPOSITION_REPLACE;
    }
  } else {
    type = COMPOSITION_INVALID;
  }

  composition_type = type;
  switch (composition_type) {
    case COMPOSITION_INVALID: {
      LOG(ERROR) << "COMPOSITION_INVALID:";
      return ERROR;
    }
    case COMPOSITION_POSITION:
    case COMPOSITION_REPLACE: {
      int32_t whole_text_length = static_cast<int32_t>(whole_text_.length());
      if (end > whole_text_length) {
        LOG(ERROR) << "composition position is larger then current text length "
                   << "end: " << end
                   << ", whole_text_length: " << whole_text_length
                   << ", preview_text_cache_ " << preview_text_cache_.length();
        return ERROR;
      }
      if (has_composition_ && !((start >= composition_range_start_) &&
                                (end <= composition_range_end_))) {
        LOG(ERROR)
            << "COMPOSITION_POSITION: start is invalid when has composition, "
            << "start: " << start << ", end: " << end
            << ", composition_range_start_: " << composition_range_start_
            << ", composition_range_end_: " << composition_range_end_;
        return ERROR;
      }
      break;
    }
    default: {
      LOG(INFO) << "no need check";
      return OK;
    }
  }
  return OK;
}

int32_t NWebInputMethodHandler::UpdateCompositionInfo(
    const std::u16string& text,
    int32_t start,
    int32_t end) {
  if (GetCompositionTypeAndCheckInput(text, start, end, composition_type_) !=
      OK) {
    LOG(ERROR) << "check in put failed";
    return ERROR;
  }

  LOG(DEBUG) << "NWebInputMethodHandler::has_composition_ " << has_composition_;
  LOG(DEBUG) << "NWebInputMethodHandler:: Before composition_range_start_ "
             << composition_range_start_ << ", composition_range_end_ "
             << composition_range_end_;
  switch (composition_type_) {
    case COMPOSITION_INVALID: {
      LOG(ERROR) << "COMPOSITION_INVALID:";
      return ERROR;
    }
    case COMPOSITION_CANCEL:
    case COMPOSITION_DELETE: {
      LOG(ERROR) << "will handle later:";
      break;
    }
    case COMPOSITION_CURRENT: {
      preview_text_cache_ = text;
      composition_cursor_index_ = text.length();
      LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_CURRENT: "
                    "composition_range_start_ "
                 << composition_range_start_ << ", composition_range_end_ "
                 << composition_range_end_ << ", composition_cursor_index_ "
                 << composition_cursor_index_;
      break;
    }
    case COMPOSITION_POSITION: {
      int32_t insert_rel_position = 0;
      if (has_composition_) {
        insert_rel_position = start - composition_range_start_;
        if (insert_rel_position < 0) {
          LOG(ERROR) << "COMPOSITION_POSITION:insert_rel_position < 0";
          return ERROR;
        }
        preview_text_cache_.insert(insert_rel_position, text);
        composition_cursor_index_ = insert_rel_position + text.length();
        LOG(DEBUG)
            << "NWebInputMethodHandler::COMPOSITION_POSITION: has_composition_ "
            << " composition_range_start_ " << composition_range_start_
            << ", composition_range_end_ " << composition_range_end_
            << ", composition_cursor_index_ " << composition_cursor_index_;
      } else {
        preview_text_cache_ = text;
        composition_cursor_index_ = text.length();
        LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_POSITION: "
                      "composition_range_start_ "
                   << composition_range_start_ << ", composition_range_end_ "
                   << composition_range_end_ << ", composition_cursor_index_ "
                   << composition_cursor_index_;
      }
      break;
    }
    case COMPOSITION_REPLACE: {
      if (has_composition_) {
        int replace_pos = start - composition_range_start_;
        if (replace_pos < 0) {
          LOG(ERROR) << "COMPOSITION_REPLACE:replace_pos < 0";
          return ERROR;
        }
        int replace_length = end - start;
        LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_REPLACE "
                   << ", replace_pos " << replace_pos << ", replace_length "
                   << replace_length;
        std::u16string replace_string =
            preview_text_cache_.replace(replace_pos, replace_length, text);
        preview_text_cache_ = replace_string;
        composition_cursor_index_ = static_cast<size_t>(replace_pos) + text.length();
      } else {
        preview_text_cache_ = text;
        composition_cursor_index_ = text.length();
      }
      LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_REPLACE: "
                    "composition_range_start_ "
                 << composition_range_start_ << ", composition_range_end_ "
                 << composition_range_end_ << ", composition_cursor_index_ "
                 << composition_cursor_index_;
      break;
    }
    default: {
      LOG(ERROR) << "invalid type:";
      return ERROR;
    }
  }
  has_composition_ = true;
  if (browser_ && browser_->GetHost()) {
    browser_->GetHost()->SetHasComposition(has_composition_);
  }
  return OK;
}

int32_t NWebInputMethodHandler::SetPreviewText(const std::u16string& text,
                                               int32_t start,
                                               int32_t end) {
  LOG(DEBUG) << "NWebInputMethodHandler::SetPreviewText start: " << start
             << ", end: " << end;
  if (UpdateCompositionInfo(std::move(text), start, end) != OK) {
    LOG(ERROR) << "update composition info failed!";
    return ERROR;
  }
  if (composition_type_ == COMPOSITION_CANCEL) {
    LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_CANCEL";
    if (browser_ == nullptr) {
      return;
    }
    CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
    if (host == nullptr) {
      return;
    }
    CefRefPtr<CefTask> task = new InputMethodTask(base::BindOnce(
        &NWebInputMethodHandler::CancelPreviewHandlerOnUI, this));
    host->PostTaskToUIThread(task);
  } else if (composition_type_ == COMPOSITION_DELETE) {
    LOG(DEBUG) << "NWebInputMethodHandler::COMPOSITION_DELETE selected_from_ "
               << selected_from_;
    if (start == selected_from_) {
      DeleteForward(1);
    }
    else if (end == selected_from_) {
      DeleteBackward(1);
    }
  } else {
    if (browser_ != nullptr && browser_->GetHost() != nullptr) {
      CefRefPtr<CefTask> task = new InputMethodTask(
          base::BindOnce(&NWebInputMethodHandler::PreviewTextHandlerOnUI, this,
                         preview_text_cache_, start, end));
      browser_->GetHost()->PostTaskToUIThread(task);
    }
  }
  return OK;
}

// LCOV_EXCL_START
void NWebInputMethodHandler::FinishTextPreview() {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  CefRefPtr<CefTask> task = new InputMethodTask(
      base::BindOnce(&NWebInputMethodHandler::FinishPreviewTextOnUI, this));
  host->PostTaskToUIThread(task);
}

void NWebInputMethodHandler::SetNeedUnderLine(bool is_need_underline) {
  SetNeedUnderLineOnUI(is_need_underline);
}
// LCOV_EXCL_STOP

#if BUILDFLAG(ARKWEB_PASSWORD_AUTOFILL)
void NWebInputMethodHandler::AutoFillWithIMFEvent(bool is_username,
                                                  bool is_other_account,
                                                  bool is_new_password,
                                                  const std::string& content) {
  if (browser_ && browser_->GetHost()) {
    CefRefPtr<CefTask> autofill_task = new InputMethodTask(base::BindOnce(
        &NWebInputMethodHandler::AutoFillWithIMFEventOnUI, this, is_username,
        is_other_account, is_new_password, std::move(content)));
    browser_->GetHost()->PostTaskToUIThread(autofill_task);
  }
}

void NWebInputMethodHandler::AutoFillWithIMFEventOnUI(
    bool is_username,
    bool is_other_account,
    bool is_new_password,
    const std::string& content) {
  if (browser_ && browser_->GetHost()) {
    browser_->GetHost()->AutoFillWithIMFEvent(is_username, is_other_account,
                                              is_new_password, content);
  }
}
#endif

// LCOV_EXCL_START
#if BUILDFLAG(ARKWEB_CLIPBOARD)
std::string NWebInputMethodHandler::GetSelectInfo() {
  std::string selected_str = base::UTF16ToUTF8(selected_text_);
  return selected_str;
}
#endif

#if BUILDFLAG(ARKWEB_AI_WRITE)
int NWebInputMethodHandler::GetSelectStartIndex()
{
  LOG(INFO) << "NWebInputMethodHandler::GetSelectStartIndex selected_from_:" << selected_from_;
  return selected_from_;
}

int NWebInputMethodHandler::GetSelectEndIndex()
{
  LOG(INFO) << "NWebInputMethodHandler::GetSelectStartIndex selected_to_:" << selected_to_;
  return selected_to_;
}

std::string NWebInputMethodHandler::GetAllTextInfo()
{
  std::string whole_str = base::UTF16ToUTF8(whole_text_);
  LOG(INFO) << "NWebInputMethodHandler::GetSelectStartIndex whole_str:" << whole_str;
  return whole_str;
}
#endif // ARKWEB_AI_WRITE

void NWebInputMethodHandler::SetWindowIdForIME(uint32_t windowId) {
  LOG(INFO) << "NWebInputMethodHandler::SetWindowIdForIME windowId: "
            << windowId;
  windowId_ = windowId;
}
// LCOV_EXCL_STOP

bool NWebInputMethodHandler::IsCorrectParam(int32_t number,
                                            int32_t& selectBegin,
                                            int32_t& selectEnd) {
  if (number < 0 || selectBegin < 0 || selectEnd < 0) {
    LOG(ERROR) << "param error, number:" << number
               << ", selectBegin:" << selectBegin
               << ", selectEnd:" << selectEnd;
    return false;
  }

  if (selectBegin > selectEnd) {
    std::swap(selectBegin, selectEnd);
  }
  if (selectEnd > static_cast<int32_t>(whole_text_.size())) {
    LOG(ERROR) << "param error, end:" << selectEnd;
    return false;
  }
  return true;
}

// LCOV_EXCL_START
bool NWebInputMethodHandler::ResetTextSelectiondata() {
  if (is_need_notify_all_) {
    LOG(ERROR) << "NWebInputMethodHandler::ResetTextSelectiondata";
    whole_text_ = u"";
    selected_from_ = 0;
    selected_to_ = 0;
    textCursorReady_ = 0;
    is_need_notify_all_ = false;
    return true;
  }
  return false;
}
// LCOV_EXCL_STOP

void NWebInputMethodHandler::HandleExtendAction(int32_t action) {
  if (browser_ == nullptr) {
    LOG(ERROR) << __FUNCTION__ << " browser is nullptr, " << action;
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    LOG(ERROR) << __FUNCTION__ << " browser host is nullptr, " << action;
    return;
  }
  CefRefPtr<CefTask> extend_action_task = new InputMethodTask(base::BindOnce(
        &NWebInputMethodHandler::HandleExtendActionOnUI, this, action));
  host->PostTaskToUIThread(extend_action_task);
}

void NWebInputMethodHandler::HandleExtendActionOnUI(int32_t action) {
  if (browser_ == nullptr) {
    return;
  }
  CefRefPtr<ArkWebBrowserHostExt> host = browser_->GetHost();
  if (host == nullptr) {
    return;
  }
  LOG(DEBUG) << __FUNCTION__ << " action is " << action;
  host->HandleInputMethodExtendAction(action);
}
}  // namespace OHOS::NWeb
